#region License
/*
Copyright (c) 2009, Open University of the Netherlands
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided 
that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the 
  following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 
  the following disclaimer in the documentation and/or other materials provided with the distribution.

* Neither the name of Open University of the Netherlands nor the names of its contributors may be 
  used to endorse or promote products derived from this software without specific prior written 
  permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
*/
#endregion

namespace EwbDesigner.NET.Classes
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using XmlTreeNode = System.Collections.Generic.SimpleTreeNode<EwbDesigner.NET.XmlCacheItem>;
    using System.Diagnostics;
    using System.Text.RegularExpressions;
    using System.Xml;
    using System.Xml.Schema;

    /// <summary>
    /// Extends SimpleTree with a ValueByName method and a ChildCount property.
    /// </summary>
    public class XmlTreeModel : SimpleTree<XmlCacheItem>, IEnumerable
    {
        #region Constructors

        /// <summary>
        /// Constructor.
        /// </summary>
        public XmlTreeModel()
            : base("name")
        {
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        public XmlTreeModel(XmlCacheItem value)
            : base("name", value)
        {
        }

        #endregion Constructors

        #region Properties

        /// <summary>
        /// ChildCount property as stored in Tree.Value, returns Tree.Value if set.
        /// </summary>
        public int ChildCount
        {
            get
            {
                if (Tree != null && Tree.Value != null)
                {
                    return Tree.Children.Count;
                }
                else
                {
                    return 0;
                }
            }
        }

        #endregion Properties

        #region Methods

        public void DumpTree()
        {
            Debug.WriteLine("");
            Debug.WriteLine("[Dump of Xml Model]");

            Debug.IndentLevel = 0;
            foreach (XmlTreeNode xmlnode in this)
            {
                Debug.IndentLevel = xmlnode.Depth;
                Debug.WriteLine(xmlnode.Path);
            }
            Debug.IndentLevel = 0;
            Debug.WriteLine("");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public IEnumerator GetEnumerator()
        {
            return Root.GetEnumerator(TreeTraversalType.DepthFirst, TreeTraversalDirection.TopDown);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public XmlTreeNode NodeByPath(String path)
        {
            if (path == null)
            {
                return (XmlTreeNode)Root;
            }

            foreach (XmlTreeNode xmlnode in this)
            {
                if (xmlnode.Path == path)
                {
                    //Trace.WriteLine("Successfull lookup of XML Path: {0}", xmlnode.Path);

                    return xmlnode;
                }
            }

            return null;
        }

        /// <summary>
        /// Searches for a path build of dot separated keys in the SoapTree.
        ///	
        /// Indexes are also supported.
        ///
        /// Sample paths:
        ///		locationInfo.description (equals locationInfo[0].description)
        ///		locationInfo[1].description
        ///		temperatureInfo[0].celcius
        /// </summary>	
        public XmlTreeNode NodeByQualifiedPath(String path)
        {
            if (path == null)
            {
                return (XmlTreeNode)Root;
            }

            //TODO: Cannot cast 'EwbDesigner.NET.XmlTreeModel' to type 'EwbDesigner.NET.XmlTreeNode'.
            XmlTreeNode stn = (XmlTreeNode)Root;

            //Debug.Log("Looking for path: " + path);

            string[] keys = path.Split(new char[] { '\\' });

            foreach (string key in keys)
            {
                bool found = false;

                //Debug.Log("Looking for: "+key);

                int index = 0;
                string keypart = key;

                //Split name[index] into key and index
                Regex rg = new Regex(@"^(?<key>[\S]*)\[(?<index>[0-9])\]$");

                //Adjust Key and Index
                Match m = rg.Match(key);
                if (m.Success)
                {
                    //Debug.Log("Key: " + m.Groups["key"].Value);
                    //Debug.Log("Index: " + m.Groups["index"].Value);

                    keypart = m.Groups["key"].Value;
                    index = int.Parse(m.Groups["index"].Value);
                }

                //^(?<key>[\S]*)$ : keyonly
                if (index == 0 && stn.Value != null)
                {
                    if (stn.Path == keypart)
                    {
                        found = true;
                    }
                }

                if (!found)
                {
                    foreach (XmlTreeNode xmlnode in stn.Children)
                    {
                        if (xmlnode.Path.EndsWith("\\" + keypart))
                        {
                            if (index == 0)
                            {
                                found = true;

                                stn = xmlnode;

                                break;
                            }
                            else
                            {
                                index--;
                            }
                        }
                    }
                }

                if (!found)
                {
                    //Debug.Log("Value not found");
                    return null;
                }
            }

            Trace.WriteLine("Successfull lookup of XSD Key: {0}", stn.Path);

            return stn;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public XmlTreeNode NodeByValue(XmlCacheItem item)
        {
            if (item == null)
            {
                return Root;
            }

            foreach (XmlTreeNode xmlnode in this)
            {
                if (xmlnode.Value == item)
                {
                    //Trace.WriteLine("Successfull lookup of XSD Key: {0}", xmlnode.Path);

                    return xmlnode;
                }
            }

            return null;
        }

        /// <summary>
        /// Save XmlModel to an Xml File an add the Xsd to it.
        /// </summary>
        /// <param name="filename"></param>
        public void ToXml(string filename, string model)
        {
            //DumpTree();

            XmlDocument doc = new XmlDocument();

            //Sample:
            //
            //<PimDotNet
            //  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            //  xsi:noNamespaceSchemaLocation="pimv2 file:///F:/Work/Visual%20Studio%202008/EwbDesigner.NET/EwbDesigner.NET/bin/Debug/Pim2.xsd">
            //

            const string xmlnsURI = @"http://www.w3.org/2000/xmlns/";

            doc.AppendChild(doc.CreateNode(XmlNodeType.XmlDeclaration, "", ""));
            XmlElement root = doc.CreateElement(Root.Value.name);

            //Fetch "http://www.w3.org/2001/XMLSchema-instance".
            XmlAttribute xmlnsXsi = doc.CreateAttribute("xmlns", "xsi", xmlnsURI);
            xmlnsXsi.Value = XmlSchema.InstanceNamespace;
            root.SetAttributeNode(xmlnsXsi);

            //Add pimv2.xsd as noNamespaceSchemaLocation.
            String xsdname = System.IO.Path.GetFullPath(model);
            xsdname = xsdname.Replace(" ", "%20");
            xsdname = xsdname.Replace('\\', '/');
            root.SetAttribute("noNamespaceSchemaLocation", XmlSchema.InstanceNamespace, "pimv2 file:///" + xsdname);

            //Append Root Node.
            XmlNode pim = doc.AppendChild(root);

            //Append the actual document.
            foreach (XmlTreeNode xmlnode in Root.Children)
            {
                ToXml(doc.DocumentElement, xmlnode);
            }

            //Save Document as show it in the WebBrowser.
            doc.Save(filename);
        }

        public void fromXml(String filename)
        {
            Clear();

            XmlDocument doc = new XmlDocument();

            doc.Load(filename);

            //XXXX Extract and Parse XSD first as we need it during reading for Attribute Types....

            //Parse "pimv2 file:///F:/Work/Visual%20Studio%202008/EwbDesigner.NET/EwbDesigner.NET/bin/Debug/Pim2.xsd"
            //to a normal full path...
            String xsdname = doc.DocumentElement.GetAttribute("noNamespaceSchemaLocation", XmlSchema.InstanceNamespace);

            xsdname = xsdname.Replace(@"file:///", "");
            xsdname = xsdname.Substring(xsdname.IndexOf(' ') + 1);
            xsdname = xsdname.Replace("%20", " ");
            xsdname = xsdname.Replace('/', '\\');
            xsdname = System.IO.Path.GetFullPath(xsdname);
            Console.WriteLine("Extracted Xsd Filename: {0}", xsdname);

            Root.Value = new XmlCacheItem(doc.DocumentElement.Name, new XmlAttributes());

            foreach (XmlNode xmlnode in doc.DocumentElement.ChildNodes)
            {
                fromXml(xmlnode, Root);
            }

            //DumpTree();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="xmlNode"></param>
        /// <param name="treeNode"></param>
        private void ToXml(XmlNode xmlNode, XmlTreeNode treeNode)
        {
            XmlNode xmlNewParent = null;

            XmlDocument doc = xmlNode.OwnerDocument;

            if (xmlNode != null)
            {
                xmlNewParent = xmlNode.AppendChild(doc.CreateElement(treeNode.Value.name));
            }
            else
            {
                xmlNewParent = doc.AppendChild(doc.CreateElement(treeNode.Value.name));
            }

            foreach (KeyValuePair<String, XmlAttributeItem> de in treeNode.Value.attributes)
            {
                String value = de.Value.value;
                if (de.Key == "titel" && String.IsNullOrEmpty(value))
                {
                    value = String.Format("[{0}]", de.Key);
                }
                xmlNewParent.Attributes.Append(doc.CreateAttribute(de.Key.ToString())).Value = value;
            }

            foreach (XmlTreeNode xmlnode in treeNode.Children)
            {
                ToXml((XmlElement)xmlNewParent, xmlnode);
            }
        }

        private void fromXml(XmlNode xmlnode, XmlTreeNode treenode)
        {
            XmlAttributes atts = new XmlAttributes();
            foreach (XmlAttribute att in xmlnode.Attributes)
            {
                //XXXX Get Correct Type and Default value from XSD....
                atts.Add(att.Name, new XmlAttributeItem(att.Name, att.Value, "String"));
            }

            XmlTreeNode newnode = (XmlTreeNode)treenode.Children.Add(new XmlCacheItem(xmlnode.Name, atts));

            foreach (XmlNode xmlchild in xmlnode.ChildNodes)
            {
                fromXml(xmlchild, newnode);
            }
        }

        #endregion Methods
    }
}